package com.miracle.flexAPI.api.service.impl;

import com.miracle.utils.ConstantsFlexApi;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.miracle.flexAPI.api.mapper.CacheUserMapper;
import com.miracle.flexAPI.api.dto.CacheUserDto;
import com.miracle.flexAPI.api.entity.CacheUser;
import com.miracle.flexAPI.api.service.CacheUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class CacheUserServiceImpl implements CacheUserService {

    private static Lock reenLock = new ReentrantLock();
    @Resource
    CacheUserMapper mapper;
    @Autowired
    RedisCache redisCache;

    private String WEB_CACHE_USER_KEY = ConstantsFlexApi.WEB_CACHE_USER_KEY;


    @Override
    public List<Map<String, Object>> selectUser(CacheUserDto dto) {
        /**
         * 1.设置有过期时间的缓存
         * 2.缓存中保存list
         */
        List<Map<String, Object>> cacheMap;
        //cacheMap = redisCache.getCacheList(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数
        cacheMap = redisCache.getCacheObject(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数

        if(cacheMap !=null && cacheMap.isEmpty()){
            cacheMap = mapper.selectUser(dto);
            if(!cacheMap.isEmpty()){
                //redisCache.setCacheList(WEB_CACHE_USER_KEY + JSONObject.toJSONString(dto), cacheMap);//设置到缓存
                //redisCache.setCacheList(WEB_CACHE_USER_KEY + dto.toString(), cacheMap);//设置到缓存
                redisCache.setCacheObject(WEB_CACHE_USER_KEY + dto.toString(), cacheMap, 30, TimeUnit.SECONDS);//设置到缓存
            }

        }


        return cacheMap;
    }

    @Override
    public AjaxResult selectUserJson(CacheUserDto dto) {
        /**
         * 首先常识从缓存获取，然后才查询数据库
         */
        Object cacheMap;
        cacheMap = redisCache.getCacheObject(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数

        if(cacheMap == null){
            List<Map<String, Object>> list = mapper.selectUser(dto);
            if(!list.isEmpty()){
                redisCache.setCacheObject(WEB_CACHE_USER_KEY + dto.toString(), list, 30, TimeUnit.SECONDS);//设置到缓存
            }
            return AjaxResult.success(list);

        }
        return AjaxResult.success(cacheMap);
    }


    @Override
    public AjaxResult selectUserCT(CacheUserDto dto) {
        /**
         * 1.缓存穿透是指在缓存系统中没有查询到数据，而不得不将请求打到数据库上查询的情况。
         * 2.正常情况下的缓存穿透是没什么伤害的，但是如果你的系统遭遇攻击，存在大量的缓存穿透的话，
         * 那么可能就是一个麻烦了，如果大量的缓存穿透超过了后端服务器的承受能力，那么就有可能造成服
         * 务崩溃，这是不可接受的。
         * 3.目前一般有两种方案：缓存空值和使用布隆过滤器。
         */

        List<Map<String, Object>> cacheMap = redisCache.getCacheObject(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数

        if(cacheMap == null){
            List<Map<String, Object>> list = mapper.selectUser(dto);
            if(!list.isEmpty()){
                redisCache.setCacheObject(WEB_CACHE_USER_KEY + dto.toString(), list, 2, TimeUnit.MINUTES);//设置到缓存
                RedisTemplate redisTemplate = redisCache.redisTemplate;
                //redisTemplate
            }else{
                /**
                 * **如果从数据库中查询到空值，就把空值写入缓存，设置较短的超时时间
                 * 虽然这种方法可以解决缓存穿透的问题，但是这种方式也存在弊端，因为在缓存系统中存了大量的空值，
                 * 浪费缓存的存储空间，如果缓存空间被占满了，还会还会剔除掉一些已经被缓存的用户信息反而会造成缓存命中率的下降。
                 * （设置空值可以预防穿透，但对于恶意攻击，效果不是很好）
                 */
                redisCache.setCacheObject(WEB_CACHE_USER_KEY + dto.toString(), list, 1, TimeUnit.MINUTES);//设置到缓存
            }
            return AjaxResult.success(list);

        }
        return AjaxResult.success(cacheMap);
    }



    @Override
    public AjaxResult selectUserJC(CacheUserDto dto) {
        /**
         * Redis Setnx（SET if Not eXists） 命令在指定的 key 不存在时，为 key 设置指定的值。
         *
         * 如果setnx 返回ok 说明拿到了锁；如果setnx 返回 nil，说明拿锁失败，被其他线程占用。和下面的reenLock一个效果
         * 用于解决缓存击穿
         */
        RedisTemplate redisTemplate = redisCache.redisTemplate;
        boolean key_mutex = redisCache.setnx("key_mutex", 1, 3 * 60, TimeUnit.SECONDS);
        log.debug(String.valueOf(key_mutex));
        boolean key_mutex2 = redisCache.setnx("key_mutex", 1, 3 * 60, TimeUnit.SECONDS);
        log.debug(String.valueOf(key_mutex2));


        /**
         * 在高并发下,多线程同时查询同一个资源,如果缓存中没有这个资源,那么这些线程都会去数据库查找,对数据库造成极大压力,
         * 缓存失去存在的意义.打个比方,数据库是人,缓存是防弹衣,子弹是线程,本来防弹衣是防止子弹打到人身上的,但是当防弹衣
         * 里面没有防弹的物质时,子弹就会穿过它打到人身上.
         * **使用互斥锁的方式 解决缓存击穿问题
         */
        List<Map<String, Object>> cacheResult = redisCache.getCacheObject(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数

        if(cacheResult == null){
            if(reenLock.tryLock()){//拿到锁才查询数据库并设置缓存
                try {
                    log.debug("我拿到锁了,从DB获取数据库后写入缓存");
                    List<Map<String, Object>> list = mapper.selectUser(dto);
                    redisCache.setCacheObject(WEB_CACHE_USER_KEY + dto.toString(), list, 2, TimeUnit.MINUTES);//设置到缓存
                    return AjaxResult.success(list);
                } finally {
                    reenLock.unlock();//释放锁
                }

            }else{//没拿到锁，再次去缓存获取，双重判断缓存是否有数
                cacheResult = redisCache.getCacheObject(WEB_CACHE_USER_KEY + dto.toString());//首先尝试去缓存取数
                if(cacheResult == null){
                    log.debug("我没拿到锁,缓存也没数据,先暂停一下");
                    try {
                        Thread.sleep(50);//两次都没拿到数据，暂停一下重试
                        return selectUserJC(dto);
                    } catch (InterruptedException e) {
                        log.error("selectUserCT", e);
                    }
                }
            }
        }
        return AjaxResult.success(cacheResult);
    }

    @Override
    public Map<String, Object> findById(CacheUserDto dto) {
        Map<String, Object> cacheMap;
        cacheMap = redisCache.getCacheMap(WEB_CACHE_USER_KEY + dto.getId());//首先尝试去缓存取数

        if(cacheMap.isEmpty()){
            List<Map<String, Object>> maps = mapper.selectUser(dto);
            if (maps.size() == 1){
                cacheMap = maps.get(0);
                redisCache.setCacheMap(WEB_CACHE_USER_KEY + dto.getId(), cacheMap);//设置到缓存
            }
        }

        return cacheMap;
    }

    @Override
    public boolean deleteCacheById(CacheUserDto dto) {
        boolean b = redisCache.deleteObject(WEB_CACHE_USER_KEY + dto.getId());
        return b;
    }

    @Override
    public int insertUser(CacheUser dto) {
        int ret = mapper.insertUser(dto);
        return ret;
    }

    @Override
    public int updateUser(CacheUser model) {
        int ret = mapper.updateUser(model);
        if(ret == 1){
            redisCache.deleteObject(WEB_CACHE_USER_KEY + model.getUserId());//内容修改，清除缓存
        }
        return ret;
    }

    @Override
    public int deleteUser(CacheUserDto dto) {
        return mapper.deleteUser(dto);
    }
}
